TypeScript is an easy to learn extension of JavaScript. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust TypeScript code.
In this article, we’ll look at the best practices to following when writing code with TypeScript, including disallowing member access on any typed variables.
Also, we look at why we want to use const
assertions.
We also look at why import
should be used instead of require
for importing modules.
We find out how we can use interfaces to specify the type of functions.
And we look at why the for-of loop is better than the regular for loop.
No Member Access on any Typed Variables
any
may leak into our codebase in nested entities.
For instance, we may have type declarations that have the any
type.
Therefore, if we allow those nested members to be accessed, we may run into type errors at runtime.
So instead of writing:
declare const anyObj: { prop: any };
anyObj.prop.a.b;
We should write:
declare const anyObj: { prop: string };
anyObj.prop;
Now that we know anyObj.prop
must be a string, we can access it safely.
Don’t Return any From a Function
We shouldn’t return anything with the any
type in a function.
Instead, we should add a return type annotation so that we know what it’s returning.
For instance, instead of writing:
function foo() {
return 1 as any;
}
or:
function arr() {
return [] as any[];
}
We should write:
function arr(): number[] {
return [1, 2];
}
or:
function foo(): number {
return 1;
}
Now we know what the functions return.
No Unused Variables and Arguments
Unused variables and arguments are useless, so we probably shouldn’t have them in our code.
We can remove variable declarations like var
, const
or let
variables that aren’t used.
Likewise, functions and classes that aren’t used can be removed.
enums, interface, and type declarations that aren’t used can also be removed.
Class members like methods, instance variables, parameters can all be removed if they aren’t used.
This also applies to import statements.
No requires Statements Except in Import Statements
We shouldn’t use require
statements any more since ES6 modules have become standard.
Therefore, instead of writing:
const foo = require('foo');
We write:
import foo = require('foo');
or:
import foo from 'foo';
Use as const Over Litreral Types
const
assertions are better than literal types since they make values constant.
If we use const
assertions, literal types can’t be widened, and object and array entries become readonly
.
For instance, instead of writing:
let bar: 2 = 2;
or:
let bar = { bar: 'baz' as 'baz' };
We can write:
let foo = 'bar' as const;
or:
let foo = { bar: 'baz' };
Use for-of Loop Instead of a for Loop
for-of loops let us loop through items with a simple loop instead of having to set up looping conditions and index variables.
It also works with any kind of iterable object instead of objects with index and the length
property.
For instance, instead of writing:
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
We write:
for (const x of arr) {
console.log(x);
}
As we can see, the for-of loop is much simpler if we just want to loop through all items in an iterable object.
Use Function Types Instead of Interfaces with Call Signatures
We can use function types instead of interfaces or object type literals with a single call signature.
For instance, instead of writing:
function foo(bar: { (): number }): number {
return bar();
}
We write:
interface Foo {
(): void;
bar: number;
}
const foo: Foo = () => {};
foo.bar = 1;
foo();
We have a function that returns nothing and has a bar
property that’s a number.
Conclusion
We may want to use as const
over literal types to prevent the type of the variable from widening and to make them, read-only.
Also, we want to use import
instead of require
for importing modules.
We also want to use interfaces instead of functions to specify the type of functions.
Finally, the for-of loop is better than the for loop in most cases.